using System;
using System.Collections.Generic;

namespace Dbam.DbFramework
{
	/// <summary>
	/// Factory that builds object relational mappers for entity types upon request.
	/// Each new entity type that is requested is cached so that it does not have to
	/// be rebuilt, improving performance (constructing ORM's can be costly).
	/// </summary>
	public class ObjectRelationalMapperFactory
	{
		#region Singleton Stuff

		/// <summary>
		/// Reference to the singleton ORM factory.
		/// </summary>
		private static ObjectRelationalMapperFactory _ormFactory;

		/// <summary>
		/// Initialize the ORM factory, which requires a database factory component.
		/// </summary>
		/// <param name="dbFactory">
		/// The database factory component that the ORM factory should
		/// use when constructing new ORM's.
		/// </param>
		public static void InitializeFactory(IDbFactory dbFactory)
		{
			if(dbFactory == null)
				throw new ArgumentNullException("dbFactory");

			_ormFactory = new ObjectRelationalMapperFactory(dbFactory);
		}

		/// <summary>
		/// Gets the ORM factory.
		/// </summary>
		public static ObjectRelationalMapperFactory Current
		{
			get
			{
				if(_ormFactory == null)
					throw new NullReferenceException(ErrorStrings.ObjectRelationalMapperFactoryNotInitialized);
				return _ormFactory;
			}
		}

		#endregion

		#region Data Members

		/// <summary>
		/// Dictionary providing quick access to the cached ORM's.
		/// </summary>
		private Dictionary<Type, object> _ormCache;

		/// <summary>
		/// The factory to use when constructing the ORM's CRUD commands.
		/// </summary>
		private IDbFactory _dbFactory;

		#endregion

		#region Constructor(s)

		/// <summary>
		/// Initialize the object relational mapper factory.
		/// </summary>
		public ObjectRelationalMapperFactory(IDbFactory dbFactory)
		{
			// Paranoia
			if(dbFactory == null)
				throw new ArgumentNullException("dbFactory");

			// Setup cache for ORM's
			_ormCache = new Dictionary<Type, object>();

			// Remember the factory component to use to construct the commands
			_dbFactory = dbFactory;
		}

		#endregion

		#region Method to fetch an ORM

		/// <summary>
		/// Gets the object relational mapper for the given entity type. The mapper
		/// is constructed if it does not exist in the cache.
		/// </summary>
		/// <param name="entityType">The entity type to fetch the ORM of.</param>
		/// <returns>The ORM compatible with the entity type.</returns>
		/// <typeparam name="OrmType">The object-relational mapper type.</typeparam>
		public OrmType GetMapper<OrmType>(Type entityType)
			where OrmType : class, new()
		{
			// Check if the ORM for the entity type is already created. If so, return that instance.
			// If the ORM is not already created, then construct one and store it in the cache.
			if(!_ormCache.ContainsKey(entityType))
			{
				Type ormBaseType = typeof(ObjectRelationalMapper<>);
				Type ormType = ormBaseType.MakeGenericType(new Type[] { ormBaseType });
				object orm = ormType.GetConstructor(Type.EmptyTypes).Invoke(new object[] { _dbFactory });
				_ormCache.Add(entityType, orm);
			}
			return _ormCache[entityType] as OrmType;
		}

		#endregion
	}
}